package net.m2technologies.open_arm.utilities;

import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.Collection;

/**
 * Copyright 2005 Mark Masterson<br> <br> Licensed under the Apache License, Version 2.0 (the "License");<br> you may
 * not use this file except in compliance with the License.<br> You may obtain a copy of the License at<br> <br>
 * http://www.apache.org/licenses/LICENSE-2.0<br> <br> Unless required by applicable law or agreed to in writing,
 * software<br> distributed under the License is distributed on an "AS IS" BASIS,<br> WITHOUT WARRANTIES OR CONDITIONS
 * OF ANY KIND, either express or implied.<br> See the License for the specific language governing permissions and<br>
 * limitations under the License.<br>
 * <p/>
 * <p>Description: </p>
 *
 * @author Mark Masterson
 * @version 0.010
 */
public final class Classes {

    private Classes() {
    }

    public static synchronized Object instantiateObject(final Class objectToInstantiate,
                                                        final Class[] constructorParameters,
                                                        final Object[] constructorParameterValues) {
        Object result = null;
        try {
            final Constructor[] constructors = objectToInstantiate.getDeclaredConstructors();
            for (int i = 0; i < constructors.length; i++) {
                final Constructor aConstructor = constructors[i];
                final Class[] params = aConstructor.getParameterTypes();
                if (constructorParameters.length == params.length) {
                    boolean matched = true;
                    matched = checkConstructorParameters(params, constructorParameters);
                    if (matched) {
                        makeObjectAccessibleForReflectionApi(aConstructor);
                        result = aConstructor.newInstance(constructorParameterValues);
                        break;
                    }
                }
            }
        } catch (Exception ex) {
            throw new RuntimeException(ex);
        }
        return result;
    }

    private static synchronized boolean checkConstructorParameters(final Class[] params,
                                                                   final Class[] constructorParameters) {
        boolean result = false;
        for (int j = 0; j < params.length; j++) {
            if (!params[j].isAssignableFrom(constructorParameters[j])) {
                result = false;
                break;
            }
        }
        return result;
    }

    /**
     * Wird ben�tigt, um an den protected oder private Konstruktor heranzukommen.
     */
    public static synchronized void makeObjectAccessibleForReflectionApi(final AccessibleObject object) {
        AccessibleObject.setAccessible(new AccessibleObject[]{object}, true);
    }

    public static synchronized Constructor obtainConstructorObject(final String clazzName,
                                                                   final Class[] constructorParametersTypes)
            throws ClassNotFoundException, NoSuchMethodException {
        final Class clazz = Class.forName(clazzName);
        return obtainConstructorObject(clazz, constructorParametersTypes);
    }

    public static synchronized Constructor obtainConstructorObject(final Class clazz,
                                                                   final Class[] constructorParametersTypes)
            throws NoSuchMethodException {
        return clazz.getDeclaredConstructor(constructorParametersTypes);
    }

    public static Collection findSubclassesOfInDeclaredClasses(final Class classWithInnerClasses,
                                                               final Class innerClassToFind) {
        final Collection result = new ArrayList();
        final Class[] innerClasses = classWithInnerClasses.getDeclaredClasses();
        Class classToCheck = null;
        for (int i = 0; i < innerClasses.length; i++) {
            classToCheck = innerClasses[i];
            if (innerClassToFind.isAssignableFrom(classToCheck) && !innerClassToFind.equals(classToCheck)) {
                result.add(classToCheck);
            }
        }
        return result;
    }

    /**
     * Findet eine Inner Class vom Typ von innerClassToFind in der �bergebenen Klasse classWithInnerClasses
     *
     * @param classWithInnerClasses
     * @param innerClassToFind
     *
     * @return gefundene Inner Class
     */
    public static Class findSubclassOfInClasses(final Class classWithInnerClasses, final Class innerClassToFind) {
        final Class[] innerClasses = classWithInnerClasses.getClasses();
        Class classToCheck = null;
        for (int i = 0; i < innerClasses.length; i++) {
            classToCheck = innerClasses[i];
            if (innerClassToFind.isAssignableFrom(classToCheck)) {
                break;
            } else {
                classToCheck = null;
            }
        }
        return classToCheck;
    }

    /**
     * Findet eine Inner Class vom Typ von innerClassToFind in der �bergebenen Klasse classWithInnerClasses
     *
     * @param classWithInnerClasses
     * @param innerClassToFind
     *
     * @return gefundene Inner Class
     */
    public static Class findSubclassOfInDeclaredClasses(final Class classWithInnerClasses,
                                                        final Class innerClassToFind) {
        final Class[] innerClasses = classWithInnerClasses.getDeclaredClasses();
        Class classToCheck = null;
        for (int i = 0; i < innerClasses.length; i++) {
            classToCheck = innerClasses[i];
            if (innerClassToFind.isAssignableFrom(classToCheck)) {
                break;
            } else {
                classToCheck = null;
            }
        }
        return classToCheck;
    }
}
